feat: 화상 면접 조회 기능 구현#84
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors session management by replacing SessionPersistenceService with a more comprehensive SessionService, adding a SessionController for session retrieval endpoints, and introducing corresponding response DTOs (SessionResponse and SessionDetailResponse). Feedback on these changes highlights opportunities to improve exception consistency by using BusinessException instead of IllegalArgumentException. Additionally, there are recommendations to prevent a potential NullPointerException when comparing creation timestamps and to address potential N+1 query performance issues by utilizing fetch joins on lazy-loaded associations.
| InterviewSession session = sessionRepository.findById(sessionId) | ||
| .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); |
There was a problem hiding this comment.
예외 처리의 일관성을 위해 IllegalArgumentException 대신 getSessionDetail 메서드에서 사용된 것과 동일하게 BusinessException(ErrorCode.SESSION_NOT_FOUND)을 던지는 것이 좋습니다.
| InterviewSession session = sessionRepository.findById(sessionId) | |
| .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); | |
| InterviewSession session = sessionRepository.findById(sessionId) | |
| .orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND)); |
| InterviewSession session = sessionRepository.findById(sessionId) | ||
| .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); |
There was a problem hiding this comment.
예외 처리의 일관성을 위해 IllegalArgumentException 대신 BusinessException(ErrorCode.SESSION_NOT_FOUND)을 던지는 것이 좋습니다.
| InterviewSession session = sessionRepository.findById(sessionId) | |
| .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); | |
| InterviewSession session = sessionRepository.findById(sessionId) | |
| .orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND)); |
| private Map<UUID, InterviewAnswer> getAnswerMap(UUID sessionId) { | ||
| return answerRepository.findBySessionId(sessionId).stream() | ||
| .collect(Collectors.toMap( | ||
| a -> a.getInterviewQuestion().getId(), | ||
| a -> a, | ||
| (existing, replacement) -> | ||
| existing.getCreatedAt().isAfter(replacement.getCreatedAt()) ? existing : replacement | ||
| )); | ||
| } |
There was a problem hiding this comment.
existing.getCreatedAt() 또는 replacement.getCreatedAt()이 null인 경우 isAfter() 호출 시 NullPointerException이 발생할 수 있습니다.
특히 JPA Auditing이 활성화되지 않은 테스트 환경이나 엔티티가 아직 영속화되지 않은 상태에서 테스트 대역(Mock)을 사용할 때 이러한 문제가 자주 발생합니다. 안전한 비교를 위해 null 체크 로직을 추가하는 것을 권장합니다.
또한, a.getInterviewQuestion().getId() 호출 시 InterviewQuestion과의 연관 관계가 지연 로딩(Lazy Loading)으로 설정되어 있다면 N+1 쿼리가 발생할 수 있으므로, answerRepository.findBySessionId(sessionId) 호출 시 페치 조인(Fetch Join)을 사용하는 것이 안전합니다.
private Map<UUID, InterviewAnswer> getAnswerMap(UUID sessionId) {
return answerRepository.findBySessionId(sessionId).stream()
.collect(Collectors.toMap(
a -> a.getInterviewQuestion().getId(),
a -> a,
(existing, replacement) -> {
if (existing.getCreatedAt() == null) return replacement;
if (replacement.getCreatedAt() == null) return existing;
return existing.getCreatedAt().isAfter(replacement.getCreatedAt()) ? existing : replacement;
}
));
}| private Map<UUID, List<InterviewFeedback>> getFeedbackMap(UUID sessionId) { | ||
| return feedbackRepository.findAllBySessionId(sessionId).stream() | ||
| .collect(Collectors.groupingBy(f -> f.getInterviewAnswer().getId())); | ||
| } |
There was a problem hiding this comment.
📌 관련 이슈 (Related Issue)
📝 작업 내용 (Description)
화상 면접 조회 기능 구현
🔄 변경 유형 (Type of Change)
✅ 체크리스트 (Checklist)